package com.godyao.mall.gateway.filter;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.godyao.mall.core.model.ResultCode;
import com.godyao.mall.core.model.ResultMsg;
import com.godyao.mall.core.model.oauth.OAuthConstant;
import com.godyao.mall.gateway.model.WhiteUrls;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 全局过滤器，对token进行拦截，解析token信息防撞到header中继，以便下游服务获取用户信息
 * 1。白名单直接放行
 * 2。校验token有效性
 * 3。解析toke中的用户信息
 * 4。重新封装用户信息成json对象，base64编码对象放到header中
 * @author godyao
 * @date 2022/4/2
 */
@Component
@Slf4j
public class GlobalAuthenticationFilter implements GlobalFilter, Ordered {
    @Autowired
    private WhiteUrls whiteUrls;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        final String requestUrl = exchange.getRequest().getPath().value();
        //final String requestUrl2 = exchange.getRequest().getURI().getPath();
        // TODO灰度发布拦截

        // 2、token不存在，但是是白名单直接放行
        final boolean white = checkUrls(whiteUrls.getUrls(), requestUrl);
        if (white) {
            //if (StrUtil.isBlank(token)) {
                // 白名单 放行
                return chain.filter(exchange);
            //}
        }
        // 1、校验token是否存在
        String token = getToken(exchange);
        // 不是白名单 token不存在在拦截
        if (StrUtil.isBlank(token)) {
            return invalidTokenMono(exchange);
        }
        // 3、判断token是否有效
        try {
            final OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
            final Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
            //令牌的唯一ID
            String jti=additionalInformation.get(OAuthConstant.JTI).toString();
            /**查看黑名单中是否存在这个jti，如果存在则这个令牌不能用****/
            Boolean hasKey = redisTemplate.hasKey(OAuthConstant.JTI_KEY_PREFIX + jti);
            if (Objects.isNull(hasKey)) {
                return invalidTokenMono(exchange);
            }
            //取出用户身份信息
            String user_name = additionalInformation.get("user_name").toString();
            //获取用户权限
            List<String> authorities = (List<String>) additionalInformation.get("authorities");
            //从additionalInformation取出userId
            String userId = additionalInformation.get(OAuthConstant.USER_ID).toString();
            Integer gender = (Integer) additionalInformation.get(OAuthConstant.GENDER);
            String nickName = additionalInformation.get(OAuthConstant.NICK_NAME).toString();
            String avatar = additionalInformation.get(OAuthConstant.AVATAR).toString();
            String mobile = additionalInformation.get(OAuthConstant.MOBILE).toString();
            String email = additionalInformation.get(OAuthConstant.EMAIL).toString();
            // 构造JSON对象
            final JSONObject jsonObject = new JSONObject();
            jsonObject.set(OAuthConstant.PRINCIPAL_NAME, user_name);
            jsonObject.set(OAuthConstant.AUTHORITIES_NAME,authorities);
            //过期时间，单位秒
            jsonObject.set(OAuthConstant.EXPR,oAuth2AccessToken.getExpiresIn());
            jsonObject.set(OAuthConstant.JTI,jti);
            //封装到JSON数据中
            jsonObject.set(OAuthConstant.USER_ID, userId);
            jsonObject.set(OAuthConstant.GENDER, gender);
            jsonObject.set(OAuthConstant.NICK_NAME, nickName);
            jsonObject.set(OAuthConstant.AVATAR, avatar);
            jsonObject.set(OAuthConstant.MOBILE, mobile);
            jsonObject.set(OAuthConstant.EMAIL, email);
            // 进行base64编码
            final String base64 = Base64.encode(jsonObject.toJSONString(0));
            // 放入请求头中
            final ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header(OAuthConstant.TOKEN_NAME, base64).build();
            final ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
            return chain.filter(build);
        } catch (Exception e) {
            log.error("token解析异常, msg={}", e.getMessage());
            //解析token异常，直接返回token无效
            return invalidTokenMono(exchange);
        }
    }

    /**
     * 返回 无效token
     * @param exchange
     * @return
     */
    private Mono<Void> invalidTokenMono(ServerWebExchange exchange) {
        return buildReturnMono(ResultMsg.resultFail(ResultCode.INVALID_TOKEN.getCode(), ResultCode.INVALID_TOKEN.getMsg()), exchange);

    }

    /**
     * 构造返回信息
     * @param resultFail
     * @param exchange
     * @return
     */
    private Mono<Void> buildReturnMono(ResultMsg<Object> resultFail, ServerWebExchange exchange) {
        final ServerHttpResponse response = exchange.getResponse();
        final byte[] bytes = JSONUtil.toJsonStr(resultFail).getBytes(StandardCharsets.UTF_8);
        final DataBuffer wrap = response.bufferFactory().wrap(bytes);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        return response.writeWith(Mono.just(wrap));
    }

    /**
     * 获取jwtToken
     * @param exchange
     * @return
     */
    private String getToken(ServerWebExchange exchange) {
        final String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StrUtil.isBlank(tokenStr)) {
            return null;
        }
        final String jwtToken = tokenStr.split(" ")[1];
        if (StrUtil.isBlank(jwtToken)) {
            return null;
        }
        return jwtToken;
    }

    /**
     * 检验当前请求在不再白名单里面
     * @param urls
     * @param requestUrl
     * @return
     */
    private boolean checkUrls(List<String> urls, String requestUrl) {
        final AntPathMatcher antPathMatcher = new AntPathMatcher();
        for (String url : urls) {
            if (antPathMatcher.match(url, requestUrl)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
